﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using Sinacor.Infra.Common.Security.Cryptography;

namespace Sinacor.Infra.Core.Licensing
{
    [Serializable]
    public class SignedLicensing
    {
        private const int MAP_SIZE = 200;

        public SignedLicensing()
        {
            //Cria chaves de criptografia específicas dessa instância
            RijndaelCryptographer.RijndaelManagedKeys rijndaelManagedKeys = new RijndaelCryptographer.RijndaelManagedKeys();

            Key = rijndaelManagedKeys.Key;
            IVKey = rijndaelManagedKeys.IVKey;
        }

        private Licensing _licensing;
        public Licensing Licensing
        {
            get { return _licensing; }
            set { _licensing = value; }
        }


        private byte[] _dencryptedLicensing = null;
        public byte[] DencryptedLicensing
        {
            set { _dencryptedLicensing = value; }
            get
            {
                // Se o objeto já estiver desencriptado, apenas retorna, caso contrário desencripta
                if (_dencryptedLicensing == null)
                    _dencryptedLicensing = Decrypt(EncryptedLicensing);
                return _dencryptedLicensing;
            }
        }


        private byte[] _encryptedLicensing = null;
        public byte[] EncryptedLicensing
        {
            set { _encryptedLicensing = value; }
            get
            {
                // Se o objeto já estiver encriptado, apenas retorna, caso contrário encripta
                if (_encryptedLicensing == null)
                {
                    byte[] bytSer = Encoding.GetEncoding("UTF-8").GetBytes(Licensing.Serialize(_licensing));
                    _encryptedLicensing = Encrypt(bytSer);
                }
                return _encryptedLicensing;
            }
        }

        private byte[] _key;
        public byte[] Key
        {
            get { return _key; }
            set { _key = value; }
        }

        private byte[] _IvKey;
        public byte[] IVKey
        {
            get { return _IvKey; }
            set { _IvKey = value; }
        }


        private byte[] _publicKey;
        public byte[] PublicKey
        {
            get { return _publicKey; }
            set { _publicKey = value; }
        }


        private byte[] _signature;
        public byte[] Signature
        {
            get { return _signature; }
            set { _signature = value; }
        }


        private byte[] Encrypt(byte[] serializedLicensing)
        {
            byte[] encryptedLicensing;

            // Transforma o array de bytes passado em uma String para encriptação
            string serializedLicensingString;
            using (MemoryStream ms = new MemoryStream(serializedLicensing))
            {
                using (StreamReader sr = new StreamReader(ms))
                {
                    serializedLicensingString = sr.ReadToEnd();
                }
            }

            // Encripta usando as chaves da instância do objeto
            encryptedLicensing = RijndaelCryptographer.EncryptString(serializedLicensingString, Key, IVKey);

            //encryptedLicensing = serializedLicensing;

            return encryptedLicensing;
        }


        // Decripta usando as chaves da instância do objeto
        // Não esquecer de setar a Key e a IVKey
        private byte[] Decrypt(byte[] encryptedLicensing)
        {
            byte[] decryptedLicensing;

            string decryptedLicensingString = RijndaelCryptographer.DecryptStringFromBytes(encryptedLicensing, Key, IVKey);
            decryptedLicensing = Encoding.GetEncoding("UTF-8").GetBytes(decryptedLicensingString);
            //decryptedLicensing = encryptedLicensing;

            return decryptedLicensing;
        }

        public byte[] GetMappedLicensingByteArray()
        {
            // Monta o Mapa de posicionamento de cada parte do arquivo de licença
            // É possível ver as partes separadamente em SaveLicensingInFiles
            LicensingMap licensingMap = CreateMap();

            // Serializa o mapa criado, para gravar dentro do próprio arquivo
            byte[] serializedMap = licensingMap.Serialize();

            // Define o tamanho do Buffer onde os dados serão escritos
            int size = EncryptedLicensing.Length + Key.Length + IVKey.Length + PublicKey.Length + Signature.Length + serializedMap.Length;

            byte[] result = new byte[size];

            // Escreve todos os dados de licença no Buffer
            using (MemoryStream ms = new MemoryStream(result))
            {
                using (BinaryWriter binaryWriter = new BinaryWriter(ms))
                {
                    binaryWriter.Write(EncryptedLicensing);
                    binaryWriter.Write(Key);
                    binaryWriter.Write(IVKey);
                    binaryWriter.Write(PublicKey);
                    binaryWriter.Write(Signature);
                    binaryWriter.Write(serializedMap); // Mapa de posicionamento no final do arquivo últimos 200 bytes (MAP_SIZE)
                    binaryWriter.Flush();
                }
            }

            return result;
        }


        public void SaveMappedLicensingFile(string licensingFile)
        {

            using (BinaryWriter binaryWriter = new BinaryWriter(
                new FileStream(licensingFile, FileMode.Create, FileAccess.ReadWrite)))
            {
                binaryWriter.Write(GetMappedLicensingByteArray());
                binaryWriter.Flush();
            }

        }

        public void SaveLicensingInFiles(string licensingFile, string keyFile, string ivKeyFile, string publicKeyFile, string signatureFile)
        {

            // Grava o arquivo com os dados de licença encriptado
            using (BinaryWriter binaryWriter = new BinaryWriter(
                new FileStream(licensingFile, FileMode.Create, FileAccess.ReadWrite)))
            {
                binaryWriter.Write(EncryptedLicensing);
                binaryWriter.Flush();
            }

            // Grava o arquivo com os dados da chave de encriptação da licença
            using (BinaryWriter binaryWriter = new BinaryWriter(
                new FileStream(keyFile, FileMode.Create, FileAccess.ReadWrite)))
            {
                binaryWriter.Write(Key);
                binaryWriter.Flush();
            }

            // Grava o arquivo com os dados da chave IV de encriptação da licença
            using (BinaryWriter binaryWriter = new BinaryWriter(
                new FileStream(ivKeyFile, FileMode.Create, FileAccess.ReadWrite)))
            {
                binaryWriter.Write(IVKey);
                binaryWriter.Flush();
            }

            // Grava o arquivo com os dados da chave pública
            using (BinaryWriter binaryWriter = new BinaryWriter(
                new FileStream(publicKeyFile, FileMode.Create, FileAccess.ReadWrite)))
            {
                binaryWriter.Write(PublicKey);
                binaryWriter.Flush();
            }

            // Grava o arquivo com os dados da assinatura
            using (BinaryWriter binaryWriter = new BinaryWriter(
                new FileStream(signatureFile, FileMode.Create, FileAccess.ReadWrite)))
            {
                binaryWriter.Write(Signature);
                binaryWriter.Flush();
            }
        }
        
        public void LoadMappedLicensingFile(string licensingFile)
        {

            // Array de bytes que vai receber todo o arquivo de licença serializado
            byte[] licensing;
            
            // Lê o arquivo de licença completo a partir de um BinaryStream manipulando um FileStream
            using (BinaryReader reader = new BinaryReader(
                new FileStream(licensingFile, FileMode.Open, FileAccess.Read)))
            {
                FileInfo info = new FileInfo(licensingFile);
                licensing = reader.ReadBytes((int)info.Length);
            }

            LoadLicensingFromByteArray(licensing);
        }

        public void LoadLicensingFromFiles(string licensingFile, string keyFile, string ivKeyFile, string publicKeyFile, string signatureFile)
        {
            // Lê o arquivo com os dados da chave de encriptação da licença
            using (BinaryReader reader = new BinaryReader(
                new FileStream(keyFile, FileMode.Open, FileAccess.Read)))
            {
                FileInfo info = new FileInfo(keyFile);
                Key = reader.ReadBytes((int)info.Length);
            }

            // Grava o arquivo com os dados da chave IV de encriptação da licença
            using (BinaryReader reader = new BinaryReader(
                new FileStream(ivKeyFile, FileMode.Open, FileAccess.Read)))
            {
                FileInfo info = new FileInfo(ivKeyFile);
                IVKey = reader.ReadBytes((int)info.Length);
            }

            // Lê o arquivo com os dados da chave pública
            using (BinaryReader reader = new BinaryReader(
                new FileStream(signatureFile, FileMode.Open, FileAccess.Read)))
            {
                FileInfo info = new FileInfo(signatureFile);
                Signature = reader.ReadBytes((int)info.Length);
            }

            // Lê o arquivo com os dados da chave pública
            using (BinaryReader reader = new BinaryReader(
                new FileStream(publicKeyFile, FileMode.Open, FileAccess.Read)))
            {
                FileInfo info = new FileInfo(publicKeyFile);
                PublicKey = reader.ReadBytes((int)info.Length);
            }

            // Lê o arquivo com os dados de licença encriptado
            using (BinaryReader reader = new BinaryReader(
                new FileStream(licensingFile, FileMode.Open, FileAccess.Read)))
            {
                FileInfo info = new FileInfo(licensingFile);
                EncryptedLicensing = reader.ReadBytes((int)info.Length);
                Licensing = Licensing.UnSerialize(DencryptedLicensing);
            }
        }

        public void LoadLicensingFromByteArray(byte[] licensing)
        {
            LicensingMap licensingMap = new LicensingMap();

            //Array de bytes que vai receber o mapa do arquivo serializado
            byte[] serializedMap = new byte[MAP_SIZE];
            
            // Lê o arquivo de licença completo a partir de um BinaryStream manipulando um MemoryStream
            // Uso de um MemorySream ao invés da leitura no trecho acima, por questão de performance (muitas mudanças de posicionamento do cursor do Stream)
            using (MemoryStream ms = new MemoryStream(licensing, true))
            {
                using (BinaryReader br = new BinaryReader(ms))
                {
                    // Lê os últimos 200 bytes do arquivo para pegar o mapa serializado
                    ms.Position = licensing.Length - serializedMap.Length;
                    serializedMap = br.ReadBytes(serializedMap.Length);
                    licensingMap.UnSerialize(serializedMap);

                    // Lê a chave de criptografia Rijndael
                    ms.Position = licensingMap.Key.Start;
                    Key = br.ReadBytes(licensingMap.Key.Finish - licensingMap.Key.Start + 1);

                    // Lê a chave IV de criptografia Rijndael
                    ms.Position = licensingMap.IVKey.Start;
                    IVKey = br.ReadBytes(licensingMap.IVKey.Finish - licensingMap.IVKey.Start + 1);

                    // Lê a assinatura gerada para os dados de licença
                    ms.Position = licensingMap.Signature.Start;
                    Signature = br.ReadBytes(licensingMap.Signature.Finish - licensingMap.Signature.Start + 1);

                    // Lê a chave pública gerada para os dados de licença
                    ms.Position = licensingMap.PublicKey.Start;
                    PublicKey = br.ReadBytes(licensingMap.PublicKey.Finish - licensingMap.PublicKey.Start + 1);

                    // Lê o arquivo de licença encriptado
                    ms.Position = licensingMap.EncryptedLicensing.Start;
                    EncryptedLicensing = br.ReadBytes(licensingMap.EncryptedLicensing.Finish - licensingMap.EncryptedLicensing.Start + 1);
                }
            }

            // Gera a licença a partir da deencriptação dos dados de licença
            Licensing = Licensing.UnSerialize(DencryptedLicensing);
        }

        // Cria um mapa de onde está cada informação no arquivo
        private LicensingMap CreateMap()
        {
            LicensingMap licMap = new LicensingMap();

            // Posição inicial e final do arquivo XML encriptado
            licMap.EncryptedLicensing.Start = 0;
            licMap.EncryptedLicensing.Finish = licMap.EncryptedLicensing.Start + (EncryptedLicensing.Length - 1);

            // Posição inicial e final da chave de encriptação do arquivo XML
            licMap.Key.Start = licMap.EncryptedLicensing.Finish + 1;
            licMap.Key.Finish = licMap.Key.Start + (Key.Length - 1);

            // Posição inicial e final da chave IV de encriptação do arquivo XML
            licMap.IVKey.Start = licMap.Key.Finish + 1;
            licMap.IVKey.Finish = licMap.IVKey.Start + (IVKey.Length - 1);

            // Posição inicial e final da chave pública para verificação da validade do arquivo
            licMap.PublicKey.Start = licMap.IVKey.Finish + 1;
            licMap.PublicKey.Finish = licMap.PublicKey.Start + (PublicKey.Length - 1);

            // Posição inicial e final da assinatura verificação da validade do arquivo
            licMap.Signature.Start = licMap.PublicKey.Finish + 1;
            licMap.Signature.Finish = licMap.Signature.Start + (Signature.Length - 1);

            return licMap;
        }
        
        [Serializable]
        private class LicensingMap
        {
            // Posição do mapa onde serão gravadas as coordenadas de início e fim da licença, chaves, etc...
            private const int LIC_POS_INI = 0;
            private const int LIC_POS_FIM = 180;

            private const int KEY_POS_INI = 140; 
            private const int KEY_POS_FIM = 80;

            private const int IVK_POS_INI = 60;
            private const int IVK_POS_FIM = 100;

            private const int PUB_POS_FIM = 40;
            private const int PUB_POS_INI = 120;

            private const int SIG_POS_FIM = 20;
            private const int SIG_POS_INI = 160;


            private Random rd = new Random();
            
            Position _licensing = new Position();
            public Position EncryptedLicensing
            {
                get { return _licensing; }
                set { _licensing = value; }
            }

            Position _key = new Position();
            public Position Key
            {
                get { return _key; }
                set { _key = value; }
            }

            Position _ivKey = new Position();
            public Position IVKey
            {
                get { return _ivKey; }
                set { _ivKey = value; }
            }

            Position _publicKey = new Position();
            public Position PublicKey
            {
                get { return _publicKey; }
                set { _publicKey = value; }
            }

            Position _signature = new Position();
            public Position Signature
            {
                get { return _signature; }
                set { _signature = value; }
            }

            public byte[] Serialize()
            {
                byte[] buffer = new byte[MAP_SIZE];
                byte[] bufferTemp = new byte[20];

                using (MemoryStream ms = new MemoryStream(buffer, true))
                {
                    // Escreve no memory stream a posição inicial da licença no arquivo
                    ms.Position = LIC_POS_INI;
                    bufferTemp = Mapfuscate(_licensing.Start.ToString(), bufferTemp.Length);
                    ms.Write(bufferTemp, 0, bufferTemp.Length);

                    // Escreve no memory stream a posição final da assinatura
                    ms.Position = SIG_POS_FIM;
                    bufferTemp = Mapfuscate(_signature.Finish.ToString(), bufferTemp.Length);
                    ms.Write(bufferTemp, 0, bufferTemp.Length);

                    // Escreve no memory stream a posição final da chave pública
                    ms.Position = PUB_POS_FIM;
                    bufferTemp = Mapfuscate(_publicKey.Finish.ToString(), bufferTemp.Length);
                    ms.Write(bufferTemp, 0, bufferTemp.Length);

                    // Escreve no memory stream a posição inicial da chave IV de criptografia
                    ms.Position = IVK_POS_INI;
                    bufferTemp = Mapfuscate(_ivKey.Start.ToString(), bufferTemp.Length);
                    ms.Write(bufferTemp, 0, bufferTemp.Length);

                    // Escreve no memory stream a posição final da chave de criptografia
                    ms.Position = KEY_POS_FIM;
                    bufferTemp = Mapfuscate(_key.Finish.ToString(), bufferTemp.Length);
                    ms.Write(bufferTemp, 0, bufferTemp.Length);

                    // Escreve no memory stream a posição final da chave IV de criptografia
                    ms.Position = IVK_POS_FIM;
                    bufferTemp = Mapfuscate(_ivKey.Finish.ToString(), bufferTemp.Length);
                    ms.Write(bufferTemp, 0, bufferTemp.Length);

                    // Escreve no memory stream a posição Inicial da chave publica para verificação da validade do arquivo
                    ms.Position = PUB_POS_INI;
                    bufferTemp = Mapfuscate(_publicKey.Start.ToString(), bufferTemp.Length);
                    ms.Write(bufferTemp, 0, bufferTemp.Length);

                    // Escreve no memory stream a posição Inicial da chave de criptografia
                    ms.Position = KEY_POS_INI;
                    bufferTemp = Mapfuscate(_key.Start.ToString(), bufferTemp.Length);
                    ms.Write(bufferTemp, 0, bufferTemp.Length);

                    // Escreve no memory stream a posição final da assinatura
                    ms.Position = SIG_POS_INI;
                    bufferTemp = Mapfuscate(_signature.Start.ToString(), bufferTemp.Length);
                    ms.Write(bufferTemp, 0, bufferTemp.Length);

                    // Escreve no memory stream a posição final da licença no arquivo
                    ms.Position = LIC_POS_FIM;
                    bufferTemp = Mapfuscate(_licensing.Finish.ToString(), bufferTemp.Length);
                    ms.Write(bufferTemp, 0, bufferTemp.Length);


                    ms.Flush();
                    ms.Position = 0;

                    // Transforma o memorystream em um array de bytes com o conteúdo do mapa
                    using (StreamReader sr = new StreamReader(ms, true))
                    {
                        buffer = Encoding.UTF8.GetBytes(sr.ReadToEnd());
                    }
                }

                return buffer;
            }

            public LicensingMap UnSerialize(byte[] serializedMap)
            {
                // A partir dos dados serializados, remonta o array de bytes
                EncryptedLicensing.Start  = GetPosition(serializedMap, LIC_POS_INI);
                EncryptedLicensing.Finish = GetPosition(serializedMap, LIC_POS_FIM);
                Key.Start        = GetPosition(serializedMap, KEY_POS_INI);
                Key.Finish       = GetPosition(serializedMap, KEY_POS_FIM);
                IVKey.Start      = GetPosition(serializedMap, IVK_POS_INI);
                IVKey.Finish     = GetPosition(serializedMap, IVK_POS_FIM);
                PublicKey.Start  = GetPosition(serializedMap, PUB_POS_INI);
                PublicKey.Finish = GetPosition(serializedMap, PUB_POS_FIM);
                Signature.Start  = GetPosition(serializedMap, SIG_POS_INI);
                Signature.Finish = GetPosition(serializedMap, SIG_POS_FIM);

                return this;
            }

            // Dentro da string ofuscada, obtem a posição exada do que está sendo solicitado
            private int GetPosition(byte[] map, int posIni)
            {
                int result;
                string strMap = String.Empty;
                int posFinal = 0;

                for (int idx = posIni; map[idx] >= 48 && map[idx] <= 57; idx++)
                    posFinal = idx;

                using (MemoryStream ms = new MemoryStream(map))
                {
                    using (StreamReader sr = new StreamReader(ms))
                    {
                        strMap = sr.ReadToEnd();
                    }
                }

                strMap = strMap.Substring(posIni, posFinal - posIni + 1);

                result = int.Parse(strMap);

                return result;
            }
            
            // Gera caracteres de 01 a 2F até completar 20 posições junto com os números de coordenada
            private byte[] Mapfuscate(string exp, int size)
            {
                byte[] result = new byte[size];
                byte[] tmp = Encoding.UTF8.GetBytes(exp);
                for (int idx = 0; idx < tmp.Length; idx++)
                    result[idx] = tmp[idx];
                for (int idx = tmp.Length; idx < size; idx++)
                    result[idx] = byte.Parse(rd.Next(1, 47).ToString());
                return result;
            }

            [Serializable]
            public class Position
            {
                int _start = -1;
                public int Start
                {
                    get { return _start; }
                    set { _start = value; }
                }

                int _finish = -1;
                public int Finish
                {
                    get { return _finish; }
                    set { _finish = value; }
                }
            }
        }
    }
}
